package com.sdk.es.http.util;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;

import com.fasterxml.jackson.databind.json.JsonMapper;
import com.sdk.es.http.conts.Conts;
import com.sdk.es.http.exception.SaasErrorEnum;
import com.sdk.es.http.exception.SaasException;
import com.sdk.es.sdk.constants.IndexConts;
import com.sdk.es.sdk.entity.common.EsBaseParams;
import com.sdk.es.sdk.entity.common.EsParam;
import com.sdk.es.sdk.enums.EsQueryKeyEnum;
import com.sdk.es.sdk.enums.SortEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author limaosheng
 * @create 2023-08-16 18:04
 * @desc
 */
@Slf4j
public class EsUtil {
    /**
     * 默认记录数，防止一次查出太多数据，导致内存溢出
     */
    public static final Integer MAX_RECORD = 1000;

    public static final JsonMapper JSON_MAPPER;

    public static final JacksonJsonpMapper JACKSON_JSONP_MAPPER;

    static {
        JSON_MAPPER = new JsonMapper();
        JACKSON_JSONP_MAPPER = new JacksonJsonpMapper(JSON_MAPPER);
    }

    /**
     * 拼接查询index以及本身es
     * @author limaosheng
     * @create 18:06 2023/8/16
     * @param index
     */
    public static IndexCoordinates getSplice(String index){
        //只有单一索引的
        if(IndexConts.onlyCurrentIndex(index)){
            return IndexCoordinates.of(index);
        }
        //有本身以及拼了年月的
        if(IndexConts.hasCurrentIndex(index)){
            return IndexCoordinates.of(index,index + Conts.ES_ALL);
        }
        //只有拼了年月的索引的
        return IndexCoordinates.of(index + Conts.ES_ALL);
    }
    /**
     * 查询指定索引
     * @author limaosheng
     * @create 17:55 2024/3/26
     * @param index
     * @param yearAndMonth
     */
    public static IndexCoordinates get(String index,String yearAndMonth){
        return IndexCoordinates.of(index + Conts.ES_R + yearAndMonth);
    }

    /**
     * 精准查询某个索引的数据
     * @author limaosheng
     * @create 14:46 2023/8/25
     * @param index
     */
    public static IndexCoordinates get(String index){
        return IndexCoordinates.of(index);
    }
    /**
     * 组装查询条件，不分页接口使用
     * @author limaosheng
     * @create 17:55 2023/8/18
     * @param esBaseParams
     */
    public static NativeQuery toNativeQuery(EsBaseParams esBaseParams){
        return toNativeQuery(esBaseParams,MAX_RECORD,null);
    }

    /**
     * 组装查询条件，分页接口使用，且pageSize和pageNum必传
     * @author limaosheng
     * @create 11:16 2024/3/11
     * @param esBaseParams
     * @param pageSize
     * @param pageNum
     */
    public static NativeQuery toNativeQuery(EsBaseParams esBaseParams, Integer pageSize, Integer pageNum){
        //参数转成builder
        BoolQuery.Builder builder = toBoolQueryBuilder(esBaseParams);
        //构建查询对象
        NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
        nativeQueryBuilder.withQuery(builder.build()._toQuery());
        if(StrUtil.isNotBlank(esBaseParams.getSort()) && StrUtil.isNotBlank(esBaseParams.getSortBy())) {
            //顺序，默认为倒序
            if(SortEnum.ASC.getKey().equals(esBaseParams.getSort())){
                nativeQueryBuilder.withSort(Sort.by(esBaseParams.getSortBy()).ascending());
            }else {
                nativeQueryBuilder.withSort(Sort.by(esBaseParams.getSortBy()).descending());
            }
        }
        //是否分页
        if(pageNum != null && pageSize != null){
            if(pageSize > MAX_RECORD){
                pageSize = MAX_RECORD;
            }
            nativeQueryBuilder.withPageable(PageRequest.of(pageNum - 1, pageSize));
        }else {
            //最大记录数默认返回1000
            nativeQueryBuilder.withMaxResults(pageSize == null ? MAX_RECORD : pageSize);
        }
        return nativeQueryBuilder.build();
    }
    /**
     * 参数转成builder
     * 直接返回builder，可另外组装未封装的查询方式
     * @author limaosheng
     * @create 16:46 2024/3/11
     * @param esBaseParams
     */
    public static BoolQuery.Builder toBoolQueryBuilder(EsBaseParams esBaseParams){
        //获取所有属性
        Field[] fields = ReflectUtil.getFields(esBaseParams.getEntity().getClass());
        //校验传入参数是否合法
        List<EsParam> esParamList = esBaseParams.getEsParamList();
        List<String> fieldNameList = new ArrayList<>();
        for(Field field : fields) {
            String fieldName = field.getName();
            fieldNameList.add(fieldName);
        }
        //排序字段校验
        if(StrUtil.isNotBlank(esBaseParams.getSort()) && StrUtil.isNotBlank(esBaseParams.getSortBy())){
            if(!fieldNameList.contains(esBaseParams.getSortBy())){
                throw new SaasException(SaasErrorEnum.PARAM_SORT_ERROR,esBaseParams.getSortBy());
            }
        }
        //参数错误，则返回异常
        for(EsParam esParam : esParamList){
            if(!fieldNameList.contains(esParam.getFieldName())){
                throw new SaasException(SaasErrorEnum.PARAM_DETAIL_ERROR,esParam.getFieldName());
            }
            //判断是否传了ID入参
            if(Conts.ID.equals(esParam.getFieldName())){
                throw new SaasException(SaasErrorEnum.ID_ERROR);
            }
            //校验esQueryKey是否符合规则
            if (!EsQueryKeyEnum.keyList().contains(esParam.getEsQueryKey())) {
                throw new SaasException(SaasErrorEnum.QUERY_DETAIL_ERROR,esParam.getEsQueryKey());
            }
            //校验参数,单个参数是否正确传参
            if(EsQueryKeyEnum.param().contains(esParam.getEsQueryKey())){
                if(esParam.getParamList() == null || esParam.getParamList().size() != 1){
                    throw new SaasException(SaasErrorEnum.ONE_PARAM_ERROR,esParam.getFieldName() + ",查询方式：" + esParam.getEsQueryKey());
                }
            }
            //校验列表参数是否传参
            if(EsQueryKeyEnum.paramList().contains(esParam.getEsQueryKey())){
                if(esParam.getParamList() == null || esParam.getParamList().size() == 0){
                    throw new SaasException(SaasErrorEnum.PARAM_NULL_ERROR,esParam.getFieldName() + ",查询方式：" + esParam.getEsQueryKey());
                }
            }
            //范围参数校验
            if(EsQueryKeyEnum.FILTER_RANGE.getKey().equals(esParam.getEsQueryKey())){
                if(esParam.getParamList() == null || esParam.getParamList().size() != 2){
                    throw new SaasException(SaasErrorEnum.PARAM_NULL_ERROR,esParam.getFieldName() + ",查询方式：" + esParam.getEsQueryKey());
                }
            }
        }
        //组装查询条件
        BoolQuery.Builder builder = QueryBuilders.bool();
        //该列表内的所有查询放在一个filter内，类似where key1 = param1 and (key2 = param2 or key3 = param3)语句and后面部分
        List<Query> shouldQuery = new ArrayList<>();
        //遍历入参
        for(EsParam esParam : esParamList){
            //判断是否要拼接keyword,字符串的统一加keyword
            for(Field f : fields){
                if(f.getName().equals(esParam.getFieldName()) && String.class.getTypeName().equals(f.getType().getTypeName())){
                    esParam.setFieldName(esParam.getFieldName() + Conts.ES_KEYWORD);
                }
            }
            //拼接查询条件
            addQuery(esParam,builder,shouldQuery);
        }
        //判断shouldQuery是否为空
        if(!shouldQuery.isEmpty()){
            builder.filter(QueryBuilders.bool().should(shouldQuery).build()._toQuery());
        }
        /**
         * 组装特殊查询条件
         * 特殊查询，类比sql：and  (( key1 = param1 and key2 = param2 ...) or ( key1 = param3 and key2 = param4 ...) or ...)
         * 适用or条件下有and查询的结构
         * 每一个Map<String,String>表示一个or查询
         */
        if(esBaseParams.getAndOrAndList() != null && !esBaseParams.getAndOrAndList().isEmpty()){
            List<Query> andOrAndQuery = new ArrayList<>();
            List<Map<String,String>> maps = esBaseParams.getAndOrAndList();
            for(Map<String, String> map : maps){
                BoolQuery.Builder andBuilder = QueryBuilders.bool();
                for(String key : map.keySet()){
                    String value = map.get(key);
                    //判断是否要拼接keyword,字符串的统一加keyword
                    for(Field f : fields){
                        if(f.getName().equals(key) && String.class.getTypeName().equals(f.getType().getTypeName())){
                            key = key + Conts.ES_KEYWORD;
                        }
                    }
                    //构建查询
                    andBuilder.filter(termQuery(key,value));
                }
                andOrAndQuery.add(andBuilder.build()._toQuery());
            }
            builder.filter(QueryBuilders.bool().should(andOrAndQuery).minimumShouldMatch("1").build()._toQuery());
        }
        return builder;
    }

    private static void addQuery(EsParam esParam, BoolQuery.Builder builder,List<Query> shouldQuery) {
        String esQueryKey = esParam.getEsQueryKey();
        /**
         * 入参查询
         */
        if(EsQueryKeyEnum.FILTER_TERM.getKey().equals(esQueryKey)){
            builder.filter(termQuery(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.FILTER_WILDCARD.getKey().equals(esQueryKey)){
            builder.filter(wildcard(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.MUST_NOT_TERM.getKey().equals(esQueryKey)){
            builder.mustNot(termQuery(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.SHOULD_TERM.getKey().equals(esQueryKey)){
            builder.should(termQuery(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.SHOULD_WILDCARD.getKey().equals(esQueryKey)){
            builder.should(wildcard(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.FILTER_SHOULD_TERM.getKey().equals(esQueryKey)){
            shouldQuery.add(termQuery(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.FILTER_SHOULD_WILDCARD.getKey().equals(esQueryKey)){
            shouldQuery.add(wildcard(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.FILTER_PREFIX.getKey().equals(esQueryKey)){
            shouldQuery.add(prefix(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        /**
         * 列表入参查询
         */
        if(EsQueryKeyEnum.FILTER_TERMS.getKey().equals(esQueryKey)){
            builder.filter(termsQuery(esParam.getFieldName(),esParam.getParamList()));
        }
        if(EsQueryKeyEnum.MUST_NOT_TERMS.getKey().equals(esQueryKey)){
            builder.mustNot(termsQuery(esParam.getFieldName(),esParam.getParamList()));
        }
        if(EsQueryKeyEnum.SHOULD_TERMS.getKey().equals(esQueryKey)){
            builder.should(termsQuery(esParam.getFieldName(),esParam.getParamList()));
        }
        if(EsQueryKeyEnum.FILTER_SHOULD_TERMS.getKey().equals(esQueryKey)){
            shouldQuery.add(termsQuery(esParam.getFieldName(),esParam.getParamList()));
        }
        /**
         * 范围查询
         */
        if(EsQueryKeyEnum.FILTER_RANGE.getKey().equals(esQueryKey)){
            queryRange(esParam, builder);
        }
        if(EsQueryKeyEnum.FILTER_LT.getKey().equals(esQueryKey)){
            builder.filter(lt(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        if(EsQueryKeyEnum.FILTER_GT.getKey().equals(esQueryKey)){
            builder.filter(gt(esParam.getFieldName(),esParam.getParamList().get(0)));
        }
        /**
         * 其它查询
         */
        if(EsQueryKeyEnum.FILTER_EXISTS.getKey().equals(esQueryKey)){
            builder.filter(existsQuery(esParam.getFieldName()));
        }
        if(EsQueryKeyEnum.MUST_NOT_EXISTS.getKey().equals(esQueryKey)){
            builder.mustNot(existsQuery(esParam.getFieldName()));
        }
        if(EsQueryKeyEnum.SHOULD_EXISTS.getKey().equals(esQueryKey)){
            builder.should(existsQuery(esParam.getFieldName()));
        }
        if(EsQueryKeyEnum.SHOULD_MUST_NOT_EXISTS.getKey().equals(esQueryKey)){
            builder.should(QueryBuilders.bool().mustNot(existsQuery(esParam.getFieldName())).build()._toQuery());
        }
        if(EsQueryKeyEnum.FILTER_SHOULD_EXISTS.getKey().equals(esQueryKey)){
            shouldQuery.add(existsQuery(esParam.getFieldName()));
        }
        if(EsQueryKeyEnum.FILTER_SHOULD_MUST_NOT_EXISTS.getKey().equals(esQueryKey)){
            shouldQuery.add(QueryBuilders.bool().mustNot(existsQuery(esParam.getFieldName())).build()._toQuery());
        }
    }
    /**
     * 范围查询，支持范围查询以及单边范围查询
     * @param esParam
     * @param builder
     */
    private static void queryRange(EsParam esParam, BoolQuery.Builder builder) {
        String from = esParam.getParamList().get(0);
        String to = esParam.getParamList().get(1);
        if(EsQueryKeyEnum.FILTER_RANGE.getKey().equals(from)){
            from = null;
        }
        if(EsQueryKeyEnum.FILTER_RANGE.getKey().equals(to)){
            to = null;
        }
        builder.filter(rangQuery(esParam.getFieldName(),from,to));
    }

    /**
     * query组装
     */
    public static <T> Query termQuery(String fieldName, T value) {
        return QueryBuilders.term().field(fieldName).value(of(value)).build()._toQuery();
    }

    public static <T> Query termsQuery(String fieldName, List<T> values) {
        TermsQueryField termsQueryField = listOf(values);
        return QueryBuilders.terms().field(fieldName).terms(termsQueryField).build()._toQuery();
    }

    public static Query toBoolQuery(List<Query> queries) {
        return QueryBuilders.bool().filter(queries) .build()._toQuery();
    }

    public static Query rangQuery(String filedName, LocalDateTime from, LocalDateTime to) {
        return QueryBuilders.range().field(filedName)
                .from(LocalDateTimeUtil.format(from, DatePattern.UTC_SIMPLE_MS_PATTERN))
                .to(LocalDateTimeUtil.format(to, DatePattern.UTC_SIMPLE_MS_PATTERN))
                .build()._toQuery();
    }

    public static Query idQuery(String id) {
        return QueryBuilders.ids().values(id).build()._toQuery();
    }

    public static Query existsQuery(String fieldName) {
        return QueryBuilders.exists().field(fieldName).build()._toQuery();
    }

    public static Query rangQuery(String filedName, String from, String to) {
        return QueryBuilders.range().field(filedName).from(from).to(to).build()._toQuery();
    }

    public static Query lt(String filedName, String value) {
        JsonData jsonData = JsonData.of(value, JACKSON_JSONP_MAPPER);
        return QueryBuilders.range().field(filedName).lt(jsonData).build()._toQuery();
    }

    public static Query gt(String filedName, String value) {
        JsonData jsonData = JsonData.of(value, JACKSON_JSONP_MAPPER);
        return QueryBuilders.range().field(filedName).gt(jsonData).build()._toQuery();
    }

    public static Query wildcard(String filedName, Object o) {
        String value = String.valueOf(o);
        value = Conts.ES_LIKE + value + Conts.ES_LIKE;
        return QueryBuilders.wildcard().field(filedName).value(value).build()._toQuery();
    }

    public static <T> TermsQueryField listOf(List<T> values) {
        List<FieldValue> fieldValues = values.stream().map(EsUtil::of).collect(Collectors.toList());
        return new TermsQueryField.Builder().value(fieldValues).build();
    }

    public static <T> FieldValue of(T value) {
        JsonData jsonData = JsonData.of(value, JACKSON_JSONP_MAPPER);
        return FieldValue.of(jsonData);
    }

    public static <T> Query prefix(String fieldName, String value) {
        return QueryBuilders.prefix().field(fieldName).value(value).build()._toQuery();
    }
}
